# Copyright (c) 2015, Frappe Technologies and contributors

import frappe
from frappe import _


class OverlapError(frappe.ValidationError): pass

def validate_overlap_for(doc, doctype, fieldname, value=None):
	"""Checks overlap for specified field.

	:param fieldname: Checks Overlap for this field
	"""

	existing = get_overlap_for(doc, doctype, fieldname, value)
	if existing:
		frappe.throw(_("This {0} conflicts with {1} for {2} {3}").format(doc.doctype, existing.name,
			doc.meta.get_label(fieldname) if not value else fieldname , value or doc.get(fieldname)), OverlapError)

def get_overlap_for(doc, doctype, fieldname, value=None):
	"""Returns overlaping document for specified field.

	:param fieldname: Checks Overlap for this field
	"""

	existing = frappe.db.sql("""select name, from_time, to_time from `tab{0}`
		where `{1}`=%(val)s and schedule_date = %(schedule_date)s and
		(
			(from_time > %(from_time)s and from_time < %(to_time)s) or
			(to_time > %(from_time)s and to_time < %(to_time)s) or
			(%(from_time)s > from_time and %(from_time)s < to_time) or
			(%(from_time)s = from_time and %(to_time)s = to_time))
		and name!=%(name)s and docstatus!=2""".format(doctype, fieldname),
		{
			"schedule_date": doc.schedule_date,
			"val": value or doc.get(fieldname),
			"from_time": doc.from_time,
			"to_time": doc.to_time,
			"name": doc.name or "No Name"
		}, as_dict=True)

	return existing[0] if existing else None


def validate_duplicate_student(students):
	unique_students= []
	for stud in students:
		if stud.student in unique_students:
			frappe.throw(_("Student {0} - {1} appears Multiple times in row {2} & {3}")
				.format(stud.student, stud.student_name, unique_students.index(stud.student)+1, stud.idx))
		else:
			unique_students.append(stud.student)

		return None

# LMS Utils
def get_current_student():
	"""Returns current student from frappe.session.user

	Returns:
		object: Student Document
	"""
	email = frappe.session.user
	if email in ('Administrator', 'Guest'):
		return None
	try:
		student_id = frappe.get_all("Student", {"student_email_id": email}, ["name"])[0].name
		return frappe.get_doc("Student", student_id)
	except (IndexError, frappe.DoesNotExistError):
		return None

def get_portal_programs():
	"""Returns a list of all program to be displayed on the portal
	Programs are returned based on the following logic
		is_published and (student_is_enrolled or student_can_self_enroll)

	Returns:
		list of dictionary: List of all programs and to be displayed on the portal along with access rights
	"""
	published_programs = frappe.get_all("Program", filters={"is_published": True})
	if not published_programs:
		return None

	program_list = [frappe.get_doc("Program", program) for program in published_programs]
	portal_programs = [{'program': program, 'has_access': allowed_program_access(program.name)} for program in program_list if allowed_program_access(program.name) or program.allow_self_enroll]

	return portal_programs

def allowed_program_access(program, student=None):
	"""Returns enrollment status for current student

	Args:
		program (string): Name of the program
		student (object): instance of Student document

	Returns:
		bool: Is current user enrolled or not
	"""
	if has_super_access():
		return True
	if not student:
		student = get_current_student()
	if student and get_enrollment('program', program, student.name):
		return True
	else:
		return False

def get_enrollment(master, document, student):
	"""Gets enrollment for course or program

	Args:
		master (string): can either be program or course
		document (string): program or course name
		student (string): Student ID

	Returns:
		string: Enrollment Name if exists else returns empty string
	"""
	if master == 'program':
		enrollments = frappe.get_all("Program Enrollment", filters={'student':student, 'program': document, 'docstatus': 1})
	if master == 'course':
		enrollments = frappe.get_all("Course Enrollment", filters={'student':student, 'course': document})

	if enrollments:
		return enrollments[0].name
	else:
		return None

@frappe.whitelist()
def enroll_in_program(program_name, student=None):
	"""Enroll student in program

	Args:
		program_name (string): Name of the program to be enrolled into
		student (string, optional): name of student who has to be enrolled, if not
			provided, a student will be created from the current user

	Returns:
		string: name of the program enrollment document
	"""
	if has_super_access():
		return

	if not student == None:
		student = frappe.get_doc("Student", student)
	else:
		# Check if self enrollment in allowed
		program = frappe.get_doc('Program', program_name)
		if not program.allow_self_enroll:
			return frappe.throw(_("You are not allowed to enroll for this course"))

		student = get_current_student()
		if not student:
			student = create_student_from_current_user()

	# Check if student is already enrolled in program
	enrollment = get_enrollment('program', program_name, student.name)
	if enrollment:
		return enrollment

	# Check if self enrollment in allowed
	program = frappe.get_doc('Program', program_name)
	if not program.allow_self_enroll:
		return frappe.throw(_("You are not allowed to enroll for this course"))

	# Enroll in program
	program_enrollment = student.enroll_in_program(program_name)
	return program_enrollment.name

def has_super_access():
	"""Check if user has a role that allows full access to LMS

	Returns:
		bool: true if user has access to all lms content
	"""
	current_user = frappe.get_doc('User', frappe.session.user)
	roles = set([role.role for role in current_user.roles])
	return bool(roles & {'Administrator', 'Instructor', 'Education Manager', 'System Manager', 'Academic User'})

@frappe.whitelist()
def add_activity(course, content_type, content, program):
	if has_super_access():
		return None

	student = get_current_student()
	if not student:
		return frappe.throw(_("Student with email {0} does not exist").format(frappe.session.user), frappe.DoesNotExistError)

	enrollment = get_or_create_course_enrollment(course, program)
	if content_type == 'Quiz':
		return
	else:
		return enrollment.add_activity(content_type, content)

@frappe.whitelist()
def evaluate_quiz(quiz_response, quiz_name, course, program, time_taken):
	import json

	student = get_current_student()

	quiz_response = json.loads(quiz_response)
	quiz = frappe.get_doc("Quiz", quiz_name)
	result, score, status = quiz.evaluate(quiz_response, quiz_name)

	if has_super_access():
		return {'result': result, 'score': score, 'status': status}

	if student:
		enrollment = get_or_create_course_enrollment(course, program)
		if quiz.allowed_attempt(enrollment, quiz_name):
			enrollment.add_quiz_activity(quiz_name, quiz_response, result, score, status, time_taken)
			return {'result': result, 'score': score, 'status': status}
		else:
			return None

@frappe.whitelist()
def get_quiz(quiz_name, course):
	try:
		quiz = frappe.get_doc("Quiz", quiz_name)
		questions = quiz.get_questions()
	except Exception:
		frappe.throw(_("Quiz {0} does not exist").format(quiz_name), frappe.DoesNotExistError)
		return None

	questions = [{
		'name': question.name,
		'question': question.question,
		'type': question.question_type,
		'options': [{'name': option.name, 'option': option.option}
					for option in question.options],
		} for question in questions]

	if has_super_access():
		return {
			'questions': questions,
			'activity': None,
			'is_time_bound': quiz.is_time_bound,
			'duration': quiz.duration
		}

	student = get_current_student()
	course_enrollment = get_enrollment("course", course, student.name)
	status, score, result, time_taken = check_quiz_completion(quiz, course_enrollment)
	return {
		'questions': questions,
		'activity': {'is_complete': status, 'score': score, 'result': result, 'time_taken': time_taken},
		'is_time_bound': quiz.is_time_bound,
		'duration': quiz.duration
	}

def get_topic_progress(topic, course_name, program):
	"""
	Return the porgress of a course in a program as well as the content to continue from.
		:param topic_name:
		:param course_name:
	"""
	student = get_current_student()
	if not student:
		return None
	course_enrollment = get_or_create_course_enrollment(course_name, program)
	progress = student.get_topic_progress(course_enrollment.name, topic)
	if not progress:
		return None
	count = sum([activity['is_complete'] for activity in progress])
	if count == 0:
		return {'completed': False, 'started': False}
	elif count == len(progress):
		return {'completed': True, 'started': True}
	elif count < len(progress):
		return {'completed': False, 'started': True}

def get_course_progress(course, program):
	"""
	Return the porgress of a course in a program as well as the content to continue from.
		:param topic_name:
		:param course_name:
	"""
	course_progress = []
	for course_topic in course.topics:
		topic = frappe.get_doc("Topic", course_topic.topic)
		progress = get_topic_progress(topic, course.name, program)
		if progress:
			course_progress.append(progress)
	if course_progress:
		number_of_completed_topics = sum([activity['completed'] for activity in course_progress])
		total_topics = len(course_progress)
		if total_topics == 1:
			return course_progress[0]
		if number_of_completed_topics == 0:
			return {'completed': False, 'started': False}
		if number_of_completed_topics == total_topics:
			return {'completed': True, 'started': True}
		if number_of_completed_topics < total_topics:
			return {'completed': False, 'started': True}

	return None

def get_program_progress(program):
	program_progress = []
	if not program.courses:
		return None
	for program_course in program.courses:
		course = frappe.get_doc("Course", program_course.course)
		progress = get_course_progress(course, program.name)
		if progress:
			progress['name'] = course.name
			progress['course'] = course.course_name
			program_progress.append(progress)

	if program_progress:
		return program_progress

	return None

def get_program_completion(program):
	topics = frappe.db.sql("""select `tabCourse Topic`.topic, `tabCourse Topic`.parent
	from `tabCourse Topic`,
		 `tabProgram Course`
	where `tabCourse Topic`.parent = `tabProgram Course`.course
			and `tabProgram Course`.parent = %s""", program.name)

	progress = []
	for topic in topics:
		topic_doc = frappe.get_doc('Topic', topic[0])
		topic_progress = get_topic_progress(topic_doc, topic[1], program.name)
		if topic_progress:
			progress.append(topic_progress)

	if progress:
		number_of_completed_topics = sum([activity['completed'] for activity in progress if activity])
		total_topics = len(progress)
		try:
			return int((float(number_of_completed_topics)/total_topics)*100)
		except ZeroDivisionError:
			return 0

	return 0

def create_student_from_current_user():
	user = frappe.get_doc("User", frappe.session.user)

	student = frappe.get_doc({
		"doctype": "Student",
		"first_name": user.first_name,
		"last_name": user.last_name,
		"student_email_id": user.email,
		"user": frappe.session.user
		})

	student.save(ignore_permissions=True)
	return student

def get_or_create_course_enrollment(course, program):
	student = get_current_student()
	course_enrollment = get_enrollment("course", course, student.name)
	if not course_enrollment:
		program_enrollment = get_enrollment('program', program.name, student.name)
		if not program_enrollment:
			frappe.throw(_("You are not enrolled in program {0}").format(program))
			return
		return student.enroll_in_course(course_name=course, program_enrollment=get_enrollment('program', program.name, student.name))
	else:
		return frappe.get_doc('Course Enrollment', course_enrollment)

def check_content_completion(content_name, content_type, enrollment_name):
	activity = frappe.get_all("Course Activity", filters={'enrollment': enrollment_name, 'content_type': content_type, 'content': content_name})
	if activity:
		return True
	else:
		return False

def check_quiz_completion(quiz, enrollment_name):
	attempts = frappe.get_all("Quiz Activity",
		filters={
			'enrollment': enrollment_name,
			'quiz': quiz.name
		},
		fields=["name", "activity_date", "score", "status", "time_taken"]
	)
	status = False if quiz.max_attempts == 0 else bool(len(attempts) >= quiz.max_attempts)
	score = None
	result = None
	time_taken = None
	if attempts:
		if quiz.grading_basis == 'Last Highest Score':
			attempts = sorted(attempts, key = lambda i: int(i.score), reverse=True)
		score = attempts[0]['score']
		result = attempts[0]['status']
		time_taken = attempts[0]['time_taken']
		if result == 'Pass':
			status = True
	return status, score, result, time_taken
